﻿using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

namespace ForgetTraffic.ServiceUtils
{
    /**********************************************************************
        *
        *  Class Name      : CrypytUtils
        *  Description     : Allocate all the crypt operations 
        * ------------------------------------------------------------------
        *  Date Created    : 28-03-2011
        *  Created By      : Vitor Viana 
        * ------------------------------------------------------------------
        *  Copyright © 2011 - ISEL,Projecto Final
        * -------------------------------------------------------------------
        *  Revision History Log
        * 
        *  Date         Author            Description
        *  _______      ______________    ___________________________________________
        *  30-03-2010   jsousa            Testing the code!! 
        * 
        **********************************************************************/

    public class CrypytUtils
    {

        public static string CalculateMD5Hash(string input)
        {
            // step 1, calculate MD5 hash from input
            MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            // step 2, convert byte array to hex string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("X2"));
            }
            return sb.ToString();
        }


        public static ComputeResult ComputeHash(String plainText,
                                                String hashAlgorithm,
                                                byte[] saltBytes)
        {
            // If salt is not specified, generate it on the fly.
            if (saltBytes == null)
            {
                // Define min and max salt sizes.
                int minSaltSize = 4;
                int maxSaltSize = 8;

                // Generate a random number for the size of the salt.
                var random = new Random();
                int saltSize = random.Next(minSaltSize, maxSaltSize);

                // Allocate a byte array, which will hold the salt.
                saltBytes = new byte[saltSize];

                // Initialize a random number generator.
                var rng = new RNGCryptoServiceProvider();

                // Fill the salt with cryptographically strong byte values.
                rng.GetNonZeroBytes(saltBytes);
            }

            // Convert plain text into a byte array.
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

            // Allocate array, which will hold plain text and salt.
            var plainTextWithSaltBytes =
                new byte[plainTextBytes.Length + saltBytes.Length];

            // Copy plain text bytes into resulting array.
            for (int i = 0; i < plainTextBytes.Length; i++)
                plainTextWithSaltBytes[i] = plainTextBytes[i];

            // Append salt bytes to the resulting array.
            for (int i = 0; i < saltBytes.Length; i++)
                plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];

            // Because we support multiple hashing algorithms, we must define
            // hash object as a common (abstract) base class. We will specify the
            // actual hashing algorithm class later during object creation.
            HashAlgorithm hash;

            // Make sure hashing algorithm name is specified.
            if (hashAlgorithm == null)
                hashAlgorithm = "";

            // Initialize appropriate hashing algorithm class.
            switch (hashAlgorithm.ToUpper())
            {
                case "SHA1":
                    hash = new SHA1Managed();
                    break;

                case "SHA256":
                    hash = new SHA256Managed();
                    break;

                case "SHA384":
                    hash = new SHA384Managed();
                    break;

                case "SHA512":
                    hash = new SHA512Managed();
                    break;

                default:
                    hash = new MD5CryptoServiceProvider();
                    break;
            }

            // Compute hash value of our plain text with appended salt.
            byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);

            // Create array which will hold hash and original salt bytes.
            var hashWithSaltBytes = new byte[hashBytes.Length +
                                             saltBytes.Length];

            // Copy hash bytes into resulting array.
            for (int i = 0; i < hashBytes.Length; i++)
                hashWithSaltBytes[i] = hashBytes[i];

            // Append salt bytes to the result.
            for (int i = 0; i < saltBytes.Length; i++)
                hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];

            // Convert result into a base64-encoded string.
            string hashValue = Convert.ToBase64String(hashWithSaltBytes);

            // Return the result.
            hashValue = EncodeSpecialCharacters(hashValue);

            return new ComputeResult {HashValue = hashValue, SaltHash = saltBytes};
        }


        public static bool VerifyHash(string plainText,
                                      string hashAlgorithm,
                                      string hashValue)
        {

            //Testar o decode
            hashValue = DecodeSpecialCharacters(hashValue);

            // Convert base64-encoded hash value into a byte array.
            byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue);
           
            // We must know size of hash (without salt).
            int hashSizeInBits, hashSizeInBytes;

            // Make sure that hashing algorithm name is specified.
            if (hashAlgorithm == null)
                hashAlgorithm = "";

            // Size of hash is based on the specified algorithm.
            switch (hashAlgorithm.ToUpper())
            {
                case "SHA1":
                    hashSizeInBits = 160;
                    break;

                case "SHA256":
                    hashSizeInBits = 256;
                    break;

                case "SHA384":
                    hashSizeInBits = 384;
                    break;

                case "SHA512":
                    hashSizeInBits = 512;
                    break;

                default: // Must be MD5
                    hashSizeInBits = 128;
                    break;
            }

            // Convert size of hash from bits to bytes.
            hashSizeInBytes = hashSizeInBits/8;

            // Make sure that the specified hash value is long enough.
            if (hashWithSaltBytes.Length < hashSizeInBytes)
                return false;

            // Allocate array to hold original salt bytes retrieved from hash.
            var saltBytes = new byte[hashWithSaltBytes.Length -
                                     hashSizeInBytes];

            // Copy salt from the end of the hash to the new array.
            for (int i = 0; i < saltBytes.Length; i++)
                saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];

            // Compute a new hash string.
            string expectedHashString =
                ComputeHash(plainText, hashAlgorithm, saltBytes).HashValue;


            expectedHashString = DecodeSpecialCharacters(expectedHashString);

            // If the computed hash matches the specified hash,
            // the plain text value must be correct.
            return (hashValue == expectedHashString);
        }

        private static String[] SPECIAL_CHARACTERS = new String[] { "?" , ";", " ", "+", "-", "=", "$", "&", "/", ",", "@", ":" };

        public static Dictionary<String, String> GetCodeDicionay()
        {
            Dictionary<String, String>  _key = new Dictionary<string, string>();
            _key.Add("?", "[1]");
            _key.Add(";", "[2]");
            _key.Add(" ", "[3]");
            _key.Add("+", "[4]");
            _key.Add("-", "[5]");
            _key.Add("=", "[6]");
            _key.Add("$", "[7]");
            _key.Add("&", "[8]");
            _key.Add("/", "[9]");
            _key.Add(",", "[q]");
            _key.Add("@", "[w]");
            _key.Add(":", "[e]");
            return _key;
        }

        public static Dictionary<String, String> GetDecodeDicionay()
        {
            Dictionary<String, String> _key = new Dictionary<string, string>();
            _key.Add("[1]","?");
            _key.Add("[2]",";");
            _key.Add("[3]"," ");
            _key.Add("[4]","+");
            _key.Add("[5]","-");
            _key.Add("[6]","=");
            _key.Add("[7]","$");
            _key.Add("[8]","&");
            _key.Add("[9]","/");
            _key.Add("[q]",",");
            _key.Add("[w]","@");
            _key.Add("[e]",":");

            return _key;
        }


        //private static string RemoveSpecialCharacters(String hashComputed)
        //{
        //    string blank = "";
        //    foreach (string special in SPECIAL_CHARACTERS)
        //    {
        //        hashComputed = hashComputed.Replace(special, blank);
        //    }
        //    return hashComputed;
        //}

        private static string EncodeSpecialCharacters(String hashComputed)
        {
            Dictionary<String, String> hash = GetCodeDicionay();
            foreach (String key in hash.Keys)
            {
                String replace = hash[key];
                hashComputed = hashComputed.Replace(key, replace);
            }
            return hashComputed;
        }

        private static string DecodeSpecialCharacters(String hashComputed)
        {
            Dictionary<String, String> hash = GetDecodeDicionay();
            foreach (String key in hash.Keys)
            {
                String replace = hash[key];
                hashComputed = hashComputed.Replace(key, replace);
            }
            return hashComputed;
        }

    }

    public class ComputeResult
    {
        public String HashValue;
        public byte[] SaltHash;
    }
}